package org.g4studio.core.net.ntp;

import java.util.List;
import java.util.ArrayList;

/**
 * Wrapper class to network time packet messages (NTP, etc) that computes
 * related timing info and stats.
 * 
 * @author Jason Mathews, MITRE Corp
 * 
 * @version $Revision: 165675 $ $Date: 2005-05-02 15:09:55 -0500 (Mon, 02 May
 *          2005) $
 */
public class TimeInfo {

	private NtpV3Packet _message;
	private List _comments;
	private Long _delay;
	private Long _offset;

	/**
	 * time at which time message packet was received by local machine
	 */
	private long _returnTime;

	/**
	 * flag indicating that the TimeInfo details was processed and delay/offset
	 * were computed
	 */
	private boolean _detailsComputed;

	/**
	 * Create TimeInfo object with raw packet message and destination time
	 * received.
	 * 
	 * @param message
	 *            NTP message packet
	 * @param returnTime
	 *            destination receive time
	 * @throws IllegalArgumentException
	 *             if message is null
	 */
	public TimeInfo(NtpV3Packet message, long returnTime) {
		this(message, returnTime, null, true);
	}

	/**
	 * Create TimeInfo object with raw packet message and destination time
	 * received.
	 * 
	 * @param message
	 *            NTP message packet
	 * @param returnTime
	 *            destination receive time
	 * @param comments
	 *            List of errors/warnings identified during processing
	 * @throws IllegalArgumentException
	 *             if message is null
	 */
	public TimeInfo(NtpV3Packet message, long returnTime, List comments) {
		this(message, returnTime, comments, true);
	}

	/**
	 * Create TimeInfo object with raw packet message and destination time
	 * received. Auto-computes details if computeDetails flag set otherwise this
	 * is delayed until computeDetails() is called. Delayed computation is for
	 * fast intialization when sub-millisecond timing is needed.
	 * 
	 * @param msgPacket
	 *            NTP message packet
	 * @param returnTime
	 *            destination receive time
	 * @param doComputeDetails
	 *            flag to pre-compute delay/offset values
	 * @throws IllegalArgumentException
	 *             if message is null
	 */
	public TimeInfo(NtpV3Packet msgPacket, long returnTime, boolean doComputeDetails) {
		this(msgPacket, returnTime, null, doComputeDetails);
	}

	/**
	 * Create TimeInfo object with raw packet message and destination time
	 * received. Auto-computes details if computeDetails flag set otherwise this
	 * is delayed until computeDetails() is called. Delayed computation is for
	 * fast intialization when sub-millisecond timing is needed.
	 * 
	 * @param message
	 *            NTP message packet
	 * @param returnTime
	 *            destination receive time
	 * @param comments
	 *            list of comments used to store errors/warnings with message
	 * @param doComputeDetails
	 *            flag to pre-compute delay/offset values
	 * @throws IllegalArgumentException
	 *             if message is null
	 */
	public TimeInfo(NtpV3Packet message, long returnTime, List comments, boolean doComputeDetails) {
		if (message == null)
			throw new IllegalArgumentException("message cannot be null");
		this._returnTime = returnTime;
		this._message = message;
		this._comments = comments;
		if (doComputeDetails)
			computeDetails();
	}

	/**
	 * Add comment (error/warning) to list of comments associated with
	 * processing of NTP parameters. If comment list not create then one will be
	 * created.
	 * 
	 * @param comment
	 */
	public void addComment(String comment) {
		if (_comments == null) {
			_comments = new ArrayList();
		}
		_comments.add(comment);
	}

	/**
	 * Compute and validate details of the NTP message packet. Computed fields
	 * include the offset and delay.
	 */
	public void computeDetails() {
		if (_detailsComputed) {
			return; // details already computed - do nothing
		}
		_detailsComputed = true;
		if (_comments == null) {
			_comments = new ArrayList();
		}

		TimeStamp origNtpTime = _message.getOriginateTimeStamp();
		long origTime = origNtpTime.getTime();

		// Receive Time is time request received by server (t2)
		TimeStamp rcvNtpTime = _message.getReceiveTimeStamp();
		long rcvTime = rcvNtpTime.getTime();

		// Transmit time is time reply sent by server (t3)
		TimeStamp xmitNtpTime = _message.getTransmitTimeStamp();
		long xmitTime = xmitNtpTime.getTime();

		/*
		 * Round-trip network delay and local clock offset (or time drift) is
		 * calculated according to this standard NTP equation:
		 * 
		 * LocalClockOffset = ((ReceiveTimestamp - OriginateTimestamp) +
		 * (TransmitTimestamp - DestinationTimestamp)) / 2
		 * 
		 * equations from RFC-1305 (NTPv3) roundtrip delay = (t4 - t1) - (t3 -
		 * t2) local clock offset = ((t2 - t1) + (t3 - t4)) / 2
		 * 
		 * It takes into account network delays and assumes that they are
		 * symmetrical.
		 * 
		 * Note the typo in SNTP RFCs 1769/2030 which state that the delay is
		 * (T4 - T1) - (T2 - T3) with the "T2" and "T3" switched.
		 */
		if (origNtpTime.ntpValue() == 0) {
			// without originate time cannot determine when packet went out
			// might be via a broadcast NTP packet...
			if (xmitNtpTime.ntpValue() != 0) {
				_offset = new Long(xmitTime - _returnTime);
				_comments.add("Error: zero orig time -- cannot compute delay");
			} else
				_comments.add("Error: zero orig time -- cannot compute delay/offset");
		} else if (rcvNtpTime.ntpValue() == 0 || xmitNtpTime.ntpValue() == 0) {
			_comments.add("Warning: zero rcvNtpTime or xmitNtpTime");
			// assert destTime >= origTime since network delay cannot be
			// negative
			if (origTime > _returnTime)
				_comments.add("Error: OrigTime > DestRcvTime");
			else {
				// without receive or xmit time cannot figure out processing
				// time
				// so delay is simply the network travel time
				_delay = new Long(_returnTime - origTime);
			}
			// TODO: is offset still valid if rcvNtpTime=0 || xmitNtpTime=0 ???
			// Could always hash origNtpTime (sendTime) but if host doesn't set
			// it
			// then it's an malformed ntp host anyway and we don't care?
			// If server is in broadcast mode then we never send out a query in
			// first place...
			if (rcvNtpTime.ntpValue() != 0) {
				// xmitTime is 0 just use rcv time
				_offset = new Long(rcvTime - origTime);
			} else if (xmitNtpTime.ntpValue() != 0) {
				// rcvTime is 0 just use xmitTime time
				_offset = new Long(xmitTime - _returnTime);
			}
		} else {
			long delayValue = _returnTime - origTime;
			// assert xmitTime >= rcvTime: difference typically < 1ms
			if (xmitTime < rcvTime) {
				// server cannot send out a packet before receiving it...
				_comments.add("Error: xmitTime < rcvTime"); // time-travel not
															// allowed
			} else {
				// subtract processing time from round-trip network delay
				long delta = xmitTime - rcvTime;
				// in normal cases the processing delta is less than
				// the total roundtrip network travel time.
				if (delta <= delayValue) {
					delayValue -= delta; // delay = (t4 - t1) - (t3 - t2)
				} else {
					// if delta - delayValue == 1 ms then it's a round-off error
					// e.g. delay=3ms, processing=4ms
					if (delta - delayValue == 1) {
						// delayValue == 0 -> local clock saw no tick change but
						// destination clock did
						if (delayValue != 0) {
							_comments.add("Info: processing time > total network time by 1 ms -> assume zero delay");
							delayValue = 0;
						}
					} else
						_comments.add("Warning: processing time > total network time");
				}
			}
			_delay = new Long(delayValue);
			if (origTime > _returnTime) // assert destTime >= origTime
				_comments.add("Error: OrigTime > DestRcvTime");

			_offset = new Long(((rcvTime - origTime) + (xmitTime - _returnTime)) / 2);
		}
	}

	/**
	 * Return list of comments (if any) during processing of NTP packet.
	 * 
	 * @return List or null if not yet computed
	 */
	public List getComments() {
		return _comments;
	}

	/**
	 * Get round-trip network delay. If null then could not compute the delay.
	 * 
	 * @return Long or null if delay not available.
	 */
	public Long getDelay() {
		return _delay;
	}

	/**
	 * Get clock offset needed to adjust local clock to match remote clock. If
	 * null then could not compute the offset.
	 * 
	 * @return Long or null if offset not available.
	 */
	public Long getOffset() {
		return _offset;
	}

	/**
	 * Returns NTP message packet.
	 * 
	 * @return NTP message packet.
	 */
	public NtpV3Packet getMessage() {
		return _message;
	}

	/**
	 * Returns time at which time message packet was received by local machine.
	 * 
	 * @return packet return time.
	 */
	public long getReturnTime() {
		return _returnTime;
	}

}
